}
# The created vector is displayed to ensure it is generated correctly:
percentage_no_data
# A data frame, invalidData, is created by passing the pollutant names and percentage_no_data vectors into the data.frame:
invalidData<-data.frame(Pollutant=pollutants,
Percentage_no_data=percentage_no_data)
# The data frame is displayed to ensure it has generated correctly:
invalidData
##################################################
# End of Answer to Question 2
##################################################
##################################################
# Answer to Question 3
##################################################
# Below, the function is created with named inputs for readability:
displayPollutantGraph<-function(pollutant, startmonth, startyear, endmonth, endyear)
{
# Below, sub sets of PortTalbotAQ are passed into vectors to later subset PortTalbot AQ to noon on the 1st of the month.
# the vector 'year' subsets data frame via substr to the first 4 characters of the Date column - extracting e.g. '2015'.
# the vector 'month' takes the 6th and 7th values e.g. 01-12 (representing January to December) ...
# where similarly 'time' extracts the first two hour digits from the 24-hour time in the 1st and 2nd position of the time column.
# each vector is then converted to a numerical value for numerical comparison purpuses used later in the function.
year<-substr(PortTalbotAQ$Date, 1, 4)
year<-as.numeric(year)
month<-substr(PortTalbotAQ$Date, 6, 7)
month<-as.numeric(month)
day<-substr(PortTalbotAQ$Date, 9, 10)
day<-as.numeric(day)
time<-substr(PortTalbotAQ$Time, 1, 2)
time<-as.numeric(time)
# The function then formulates a 6 digit number in the format YYYYMM by multiplying the year by 100, and adding the month ...
# to create, for example, 201501 representing the 1st of January 2015.
# This is used later in the function to handle date comparisons numerically.
full_date<- year * 100 + month
# The function full start date input by the user is calculated as above:
user_start_date<- startyear * 100 + startmonth
# The function end date input by the user is calculated:
user_end_date <- endyear * 100 + endmonth
# Below, two validation inputs are included to prevent input of dates outside of the data set.
# To achieve this, 'stop' is utilized which displays an error message if an invalid input is included.
if (user_start_date < 201501) {
stop("Invaid input, less than range of data set.")
}
if (user_end_date> 202412) {
stop("Invalid input, higher than range of data set.")
}
# A logical vector,  noon_1st, is created by comparing date and time values to identify rows where measurements ...
# ... were taken at noon on the 1st of each month.
noon_1st<-(day == 1) & (time == 12)
# A second logical vector, within_range, is created to identify rows where the specific observation falls between ...
# .. and inclusive of, the dates of input. This is achieved by filtering the range required to that inputted by the user ...
# Thereby creating a TRUE/FALSE vector of date values falling within the specified time range.
within_range<-(full_date >= user_start_date) & (full_date <= user_end_date)
# The two logical vectors are now combined to create a logical vector of dates in range and at the required day and time:
retain<-noon_1st & within_range
# A vector date is populated with, in date format, the actual values from the date column of PortTalbotAQ ...
# .. now subset in comparison to the TRUE/FALSE vector of retain, e.g. where the date is noon on the first of the month ...
# .. and the date falls within the specified range.
date<-as.Date(PortTalbotAQ$Date[retain])
# The following 6 lines of script filter out non-numeric values from the input Pollutant as follows:
# 1. A vector is created with all values of the input pollutant:
pollutant_v_temp<-PortTalbotAQ[[pollutant]]
# 2. A vector is created which filters the temporary pollutant vector to the value specified in the logical vector retain:
retained_pollutant<-(pollutant_v_temp[retain])
# 3. The values in this vector containing "No data" are replaced with NA:
retained_pollutant[retained_pollutant=="No data"]<-NA
# The vector is converted to a numeric vector:
retained_pollutant<-as.numeric(retained_pollutant)
# 5. A vector is created by filtering the pollutant vector to all values excluding NA:
remove_na_figures_pollutant<-!is.na(retained_pollutant)
# 6. Date and pollutant are then subset to only non-NA values:
date<-date[remove_na_figures_pollutant]
retained_pollutant<-retained_pollutant[remove_na_figures_pollutant]
# 7. Below ggplot is used to set line and point colours, and labeling is added:
displayPollutantGraph_DF<-data.frame(Date=date, pollutant_value=retained_pollutant)
ggplot(displayPollutantGraph_DF, aes(x = Date, y = pollutant_value)) +
geom_point(colour="blue", size = 1) +
geom_smooth(method = "lm") +
labs(
title = paste(pollutant, " levels at 12:00 on the 1st of the month"),
subtitle = paste("From ", startmonth,"/", startyear," to ", endmonth,"/",endyear),
x = "Date",
y = paste0(pollutant, " level")
) +
theme_minimal()
}
# The function is executed below using the NO_as_NO2 column, commencing July 2015 through to November 2024:
displayPollutantGraph("NO_as_NO2", 7, 2015, 11, 2024)
##################################################
# End of Answer to Question 3
##################################################
##################################################
# Answer to Question 4
##################################################
# The below function is created by naming a pollutant vector as its input:
comparePollutants<-function(pollutantvector) {
# The below year, month, and day logic mirrors that in Q3:
year<-substr(PortTalbotAQ$Date, 1, 4)
year<-as.numeric(year)
month<-substr(PortTalbotAQ$Date, 6, 7)
month<-as.numeric(month)
day<-substr(PortTalbotAQ$Date, 9, 10)
day<-as.numeric(day)
time<-substr(PortTalbotAQ$Time, 1, 2)
time<-as.numeric(time)
noon_1st<-(day == 1) & (time == 12)
# Dates are fitered below to noon_1st as with Q3:
filtered_date<-as.Date(PortTalbotAQ$Date[noon_1st])
# Below, a for loop is used as follows:
# 1. For each value in the pollutant vector, a temporary vector stores the pollutant column with the same name from PortTalbotAQ.
# 2. "No Data" values in the vector are changed to NA.
# 3. pv_temp is stored with the values filtered to noon_1st
# 4. pv_temp is added to the above data frame alongside the date column.
result<-data.frame(Date=filtered_date)
for (i in pollutantvector) {
pv_temp<-PortTalbotAQ[[i]]
pv_temp[pv_temp=="No data"]<-NA
pv_temp<-as.numeric(pv_temp)[noon_1st]
result[[i]]<-pv_temp
}
# Below, ggplot is utilised to add lines for each pollutant to the graph, with titles and a legend also added:"
ggplot(result, aes(x = Date)) +
geom_line(aes(y = O3, colour = pollutantvector[1]), linewidth = 0.7, alpha=0.85) +
geom_line(aes(y = NO2, colour = pollutantvector[2]), linewidth = 0.7, alpha=0.85) +
geom_line(aes(y = SO2, colour = pollutantvector[3]), linewidth = 0.7, alpha=0.85) +
geom_line(aes(y = PM10, colour = pollutantvector[4]), linewidth = 0.7, alpha=0.85) +
labs(
title = "Pollutant levels at Port Talbot (Noon on 1st of each month)",
x = "Date",
y = "Pollutant level - gaps = no data",
colour = "Pollutant"
) +
theme_minimal()
}
# Below, a pollutant vector is generated and populated in the function:
pollutant_v_for_fn<-c("O3", "NO2", "SO2", "PM10")
comparePollutants(pollutant_v_for_fn)
##################################################
# End of Answer to Question 4
##################################################
##################################################
# Answer to Question 5
##################################################
# The below function is created without any arguements,  as the function calculates a specific correlation ....
# ... betweeen two pollutants.
O3NOCorrelation<-function() {
# The below extracts the relevant columns,  replacing with NAs and setting as numeric:
O3_values<-PortTalbotAQ[["O3"]]
O3_values[O3_values == "No data"]<- NA
O3_values<-as.numeric(O3_values)
NO2_values<-PortTalbotAQ[["NO2"]]
NO2_values[NO2_values == "No data"]<- NA
NO2_values<-as.numeric(NO2_values)
# complete_cases is used below to remove NA rows across both sub sets:
non_NA_values<-complete.cases(O3_values, NO2_values)
# The below filters the two sub sets to non-NA values:
O3_retained<-O3_values[non_NA_values]
NO2_retained<-NO2_values[non_NA_values]
# Correlation is applied to the retained sub sets:
corr<-cor(O3_retained, NO2_retained)
# A data frame is created for plotting:
corr_df<-data.frame(NO2=NO2_retained, O3=O3_retained)
corr_df<-subset(corr_df, NO2<=100)
# ggplot is used to create a scatter plot via geom_point. Alpha sets transparency ...
# Where size and colour control point size and colour.
# A trend line is added via geom_smooth.
plot<-ggplot(corr_df, aes(x = NO2, y = O3)) +
geom_point(alpha = 0.2, size = 0.7, colour = "blue") +
geom_smooth(method = "loess", se=FALSE, colour="red") +
labs(
title = "O3 vs NO2 at Port Talbot (2015–2024)",
x = "NO2 level",
y = "O3 level"
) +
theme_minimal()
# The ggplot and correlation are returned to the main script here:
print(plot)
return(corr)
}
# The function is called here:
O3NOCorrelation()
##################################################
# End of Answer to Question 5
##################################################
##################################################
# Answer to Question 6
##################################################
# Below, O3 and windspeed values are extracted from PortTalbotAQ, NAs are introduced, and the data set is set as numeric:
O3_values_uncleaned<-PortTalbotAQ[["O3"]]
O3_values_uncleaned[O3_values_uncleaned=="No data"]<-NA
O3_values_cleaned<-as.numeric(O3_values_uncleaned)
windspeed_values_uncleaned<-PortTalbotAQ[["WindSpeed"]]
windspeed_values_uncleaned[windspeed_values_uncleaned=="No data"]<-NA
windspeed_values_cleaned<-as.numeric(windspeed_values_uncleaned)
# Below, only complete cases are retained - a logical vector is created where NA rows are excluded:
retained_values<-complete.cases(O3_values_cleaned, windspeed_values_cleaned)
# Below returns only values from the original numeric vectors ...
# .. corresponding to TRUE values in retained_values, thereby creating vectors of numeric non NA values only:
O3_final_values<-O3_values_cleaned[retained_values]
windspeed_final_values<-windspeed_values_cleaned[retained_values]
# The below creates two vectors for t-testing by filtering - this splits O3_final_values ...
# .. into two numeric vectors with windspeed values less than and more than or equal to 5ms.
windspeed_under_5ms<-O3_final_values[windspeed_final_values<5]
windspeed_over_5ms<-O3_final_values[windspeed_final_values>=5]
# Having now extracted the relevant data into 2 sets, a Shapiro test was considered in order to test ...
# if the data was normally distributed, however the large data set prevented the test from executing.
# The writer's research suggests that t-testing is robust for larger data sets.
# In order to explore the data set further, box plotting is carried out below to identify ...
# Outliers in the data sets:
boxplot(windspeed_under_5ms)
boxplot(windspeed_over_5ms)
# Box plotting show several outliers in ozone concentrations in the 100+ range (a right skewness of the data sets).
# To reduce outliers in the t-test, all values above 100 have are removed.
# The purpose of this is to assist in both data sets following a less skewed distribution, thereby suiting the t-test.
windspeed_under_5ms<- windspeed_under_5ms[windspeed_under_5ms<100]
windspeed_over_5ms<- windspeed_over_5ms[windspeed_over_5ms<100]
# Prior to performing the t-test, a set of hypothesis must be set.
# For the data sets at hand, these will be as follows:
# H0 = There is no statistically significant difference between mean Ozone levels ...
# ... when windspeed is under 5ms-1 and equal to or greater than 5ms-1.
# H1 = There is a statistically significant difference between mean Ozone levels ...
# ... when windspeed is under 5ms-1 and when windspeed is equal to or greater than 5ms-1.
# In order to reject H0, the p-value result of the t-test would need to fall below 0.05.
t_test_result<-t.test(windspeed_under_5ms, windspeed_over_5ms)
t_test_result
# The result of the t-test p value = 2.2e-16, meaning the p value falls well below 0.05.
# The result allows a rejection of the null-hypothesis and an acceptance of H1 ...
# ... Meaning there is a statistically significant difference found as per H1.
##################################################
# End of Answer to Question 6
##################################################
##################################################
# Answer to Question 7
##################################################
# Below, NO2 values are extracted, NAs are convered from No Data, and the filtered data set is set to numeric:
NO2_uncleaned<-PortTalbotAQ[["NO2"]]
NO2_uncleaned[NO2_uncleaned=="No data"]<-NA
NO2_cleaned<-as.numeric(NO2_uncleaned)
# Below, year/month/day is extracted as above:
year<-substr(PortTalbotAQ$Date, 1, 4)
year<-as.numeric(year)
month<-substr(PortTalbotAQ$Date, 6, 7)
month<-as.numeric(month)
day<-substr(PortTalbotAQ$Date, 9, 10)
day<-as.numeric(day)
time<-substr(PortTalbotAQ$Time, 1, 2)
time<-as.numeric(time)
# A logical filter is then applied for the required timeframe:
retained_data<-(time == 17) & (month %in% c(11,12) & year %in% c(2016, 2018, 2020))
# A data frame is then created with factor creating 3 categories (years). NAs are then removed:
anova_DF<-data.frame(year=factor(year[retained_data]), NO2 = NO2_cleaned[retained_data])
anova_DF<-anova_DF[!is.na(anova_DF$NO2), ]
anova_DF
# Having now created a data frame for ANOVA testing, the hypotheses for the ANOVA test must be set as follows:
# H0 = There is no statistically significant differences in the mean levels of nitrogen dioxide in the three periods ...
# ... of Nov-Dec of 2016, 2018, and 2020.
# H1 = There is a statistically significant difference in mean NO2 levels between, at least, 2 of the three periods identified.
# The null hypothesis can be rejected only if the value of PR(>F) falls below 0.05.
# Below, the ANOVA test is run using the data frame created above, and a summary is printed:
anova<-aov(NO2 ~ year, data=anova_DF)
summary(anova)
# The ANOVA test provided a p-value result of 7.2e-07.
# This result is smaller than 0.05, meaning that there is a statistical significance in the mean difference of at least ...
# .. two of the three years compared in the ANOVA test.
# In order to identify which year(s) show a statistically significant difference ...
# A Tukey test can identify where the difference lies.
TukeyHSD(anova)
# The output of the Tukey test reveals statistically significant variance in the years 2018-2016 and ...
#.. in the years 2020 - 2016 (p adj < 0.05), however, not in 2020-2018 (p adj >0.05).
# The result identifies that for the period tested, NO2 levels were statistically significantly lower ...
# ... in 2018 compared to 2016, and 2020 compared to 2016, but not between 2020 and 2018.
# The testing conducted allows for a rejection of H0, with post-hoc testing establishing that the statistically ...
# ... significant difference lies between 2016 and the two other years.
##################################################
# End of Answer to Question 7
##################################################
View(PortTalbotAQ)
View(invalidData)
View(displayPollutantGraph)
View(comparePollutants)
View(O3NOCorrelation)
O3NOCorrelation()
library(readxl)
library(dplyr)
library(tidyr)
library(lubridate)
library(ggplot2)
try({
if (requireNamespace("rstudioapi", quietly = TRUE)) {
this_file <- rstudioapi::getActiveDocumentContext()$path
if (nzchar(this_file)) {
setwd(dirname(this_file))
}
}
}, silent = TRUE)
data_file <- "Port Talbot Hourly AQ.xlsx"
PortTalbotAQ_raw <- read_excel(data_file, skip = 10, col_types = "text")
PortTalbotAQ_raw <- read_excel("Port Talbot Hourly AQ.xlsx", skip = 10, col_types = "text")
setwd("C:/Users/soac-nw/Dropbox/2025-2026/Marking/S1 - Statistical Programming/Assignment 2")
PortTalbotAQ <- PortTalbotAQ_raw %>%
rename(
date = `Date`,
time = `Time`,
o3  = `Ozone`,
no  = `Nitric oxide`,
no2 = `Nitrogen dioxide`,
nox = `Nitrogen oxides as nitrogen dioxide`,
so2 = `Sulphur dioxide`,
co  = `Carbon monoxide`,
pm10 = `PM10 particulate matter (Hourly measured)`,
pm25 = `PM2.5 particulate matter (Hourly measured)`,
wind_dir   = `Modelled Wind Direction`,
wind_speed = `Modelled Wind Speed`,
temp       = `Modelled Temperature`
)
PortTalbotAQ_raw <- read_excel("Port Talbot Hourly AQ.xlsx", skip = 10, col_types = "text")
PortTalbotAQ <- PortTalbotAQ_raw %>%
rename(
date = `Date`,
time = `Time`,
o3  = `Ozone`,
no  = `Nitric oxide`,
no2 = `Nitrogen dioxide`,
nox = `Nitrogen oxides as nitrogen dioxide`,
so2 = `Sulphur dioxide`,
co  = `Carbon monoxide`,
pm10 = `PM10 particulate matter (Hourly measured)`,
pm25 = `PM2.5 particulate matter (Hourly measured)`,
wind_dir   = `Modelled Wind Direction`,
wind_speed = `Modelled Wind Speed`,
temp       = `Modelled Temperature`
)
PortTalbotAQ <- PortTalbotAQ %>%
mutate(
date = as.Date(date),
time = format(as.POSIXct(time, format = "%H:%M:%S"), "%H:%M:%S"),
datetime = as.POSIXct(paste(date, time), format = "%Y-%m-%d %H:%M:%S", tz = "UTC")
)
cat("\n---------------- PortTalbotAQ data frame ----------------\n")
print(PortTalbotAQ)
cat("\nRows:", nrow(PortTalbotAQ), "  Columns:", ncol(PortTalbotAQ), "\n")
if (interactive()) {
View(PortTalbotAQ)
}
make_numeric <- function(x) {
x <- as.character(x)
x <- trimws(x)
x[x == "No data"] <- NA
x[x == ""] <- NA
as.numeric(x)
}
pollutants <- c("o3", "no", "no2", "nox", "so2", "co", "pm10", "pm25")
invalid_percent <- c()
for (p in pollutants) {
column_values <- as.character(PortTalbotAQ[[p]])
column_values <- trimws(column_values)
missing_flags <- is.na(column_values) | column_values == "No data" | column_values == ""
invalid_percent[p] <- mean(missing_flags) * 100
}
invalidData <- data.frame(
pollutant = names(invalid_percent),
percent_no_measurement = as.numeric(invalid_percent),
row.names = NULL
)
cat("\n---------------- invalidData (% No data) ----------------\n")
print(invalidData)
if (interactive()) {
View(invalidData)
}
displayPollutantGraph <- function(pollutant_name, start_month, start_year, end_month, end_year) {
if (!(pollutant_name %in% names(PortTalbotAQ))) {
stop("That pollutant column name is not in the data frame.")
}
start_date <- as.Date(paste0(start_year, "-", sprintf("%02d", start_month), "-01"))
end_date <- as.Date(paste0(end_year, "-", sprintf("%02d", end_month), "-01"))
end_date <- ceiling_date(end_date, "month") - days(1)
df <- PortTalbotAQ %>%
filter(date >= start_date, date <= end_date) %>%
filter(day(date) == 1) %>%
filter(time == "12:00:00") %>%
mutate(value = make_numeric(.data[[pollutant_name]])) %>%
filter(!is.na(value)) %>%
arrange(date)
ggplot(df, aes(x = date, y = value)) +
geom_line() +
geom_point() +
labs(
title = paste("Pollutant levels at noon on the 1st:", pollutant_name),
x = "Date",
y = pollutant_name
)
}
cat("\n--- Task 3 graph: NOx (as NO2) from July 2015 to Nov 2024 ---\n")
print(displayPollutantGraph("nox", 7, 2015, 11, 2024))
comparePollutants <- function(pollutant_vector) {
for (p in pollutant_vector) {
if (!(p %in% names(PortTalbotAQ))) {
stop(paste("Pollutant not found:", p))
}
}
df <- PortTalbotAQ %>%
filter(date >= as.Date("2015-01-01"), date <= as.Date("2024-12-31")) %>%
filter(day(date) == 1, time == "12:00:00") %>%
select(date, all_of(pollutant_vector))
df_long <- pivot_longer(df, cols = all_of(pollutant_vector),
names_to = "pollutant", values_to = "raw")
df_long <- df_long %>%
mutate(value = make_numeric(raw)) %>%
filter(!is.na(value))
ggplot(df_long, aes(x = date, y = value, linetype = pollutant)) +
geom_line() +
labs(
title = "Pollutant comparison (noon on 1st of month, 2015–2024)",
x = "Date",
y = "Level",
linetype = "Pollutant"
)
}
cat("\n--- Task 4 graph: compare 4 pollutants ---\n")
print(comparePollutants(c("o3", "no2", "pm10", "pm25")))
O3NOCorrelation <- function() {
df <- PortTalbotAQ %>%
transmute(
o3_num = make_numeric(o3),
no2_num = make_numeric(no2)
) %>%
filter(!is.na(o3_num), !is.na(no2_num))
r_value <- cor(df$o3_num, df$no2_num)
cat("\n--- Task 5: Correlation between O3 and NO2 ---\n")
cat("Pearson correlation =", r_value, "\n")
ggplot(df, aes(x = no2_num, y = o3_num)) +
geom_point(alpha = 0.4) +
labs(
title = "O3 vs NO2 scatterplot",
subtitle = paste("Correlation =", round(r_value, 4)),
x = "NO2",
y = "O3"
)
}
print(O3NOCorrelation())
df_t <- PortTalbotAQ %>%
transmute(
o3_num = make_numeric(o3),
ws_num = make_numeric(wind_speed)
) %>%
filter(!is.na(o3_num), !is.na(ws_num)) %>%
mutate(wind_group = ifelse(ws_num < 5, "<5", ">=5"))
t_test_result <- t.test(o3_num ~ wind_group, data = df_t)
cat("\n--- Task 6: t-test result ---\n")
print(t_test_result)
if (t_test_result$p.value < 0.05) {
cat("Conclusion: p < 0.05 so I reject H0 (there is a significant difference).\n")
} else {
cat("Conclusion: p >= 0.05 so I do not reject H0 (no significant difference found).\n")
}
df_aov <- PortTalbotAQ %>%
filter(time == "17:00:00") %>%
filter(year(date) %in% c(2016, 2018, 2020)) %>%
filter(month(date) %in% c(11, 12)) %>%
transmute(
year = factor(year(date)),
no2_num = make_numeric(no2)
) %>%
filter(!is.na(no2_num))
anova_model <- aov(no2_num ~ year, data = df_aov)
cat("\n--- Task 7: ANOVA result ---\n")
print(summary(anova_model))
p_value_anova <- summary(anova_model)[[1]][["Pr(>F)"]][1]
if (!is.na(p_value_anova) && p_value_anova < 0.05) {
cat("ANOVA p < 0.05 so I reject H0. Now I will do Tukey HSD to see which years differ.\n")
print(TukeyHSD(anova_model))
} else {
cat("ANOVA p >= 0.05 so I do not reject H0. I will not do post-hoc tests.\n")
}
data_file <- "Port Talbot Hourly AQ.xlsx"
PortTalbotAQ_raw <- read_excel(data_file, skip = 10, col_types = "text")
data_file <- "Port Talbot Hourly AQ.xlsx"
PortTalbotAQ_raw <- read_excel(data_file, skip = 10, col_types = "text")
View(invalidData)
View(invalidData)
View(comparePollutants)
t_test_result <- t.test(o3_num ~ wind_group, data = df_t)
cat("\n--- Task 6: t-test result ---\n")
print(t_test_result)
if (t_test_result$p.value < 0.05) {
cat("Conclusion: p < 0.05 so I reject H0 (there is a significant difference).\n")
} else {
cat("Conclusion: p >= 0.05 so I do not reject H0 (no significant difference found).\n")
}
df_aov <- PortTalbotAQ %>%
filter(time == "17:00:00") %>%
filter(year(date) %in% c(2016, 2018, 2020)) %>%
filter(month(date) %in% c(11, 12)) %>%
transmute(
year = factor(year(date)),
no2_num = make_numeric(no2)
) %>%
filter(!is.na(no2_num))
anova_model <- aov(no2_num ~ year, data = df_aov)
cat("\n--- Task 7: ANOVA result ---\n")
print(summary(anova_model))
p_value_anova <- summary(anova_model)[[1]][["Pr(>F)"]][1]
if (!is.na(p_value_anova) && p_value_anova < 0.05) {
cat("ANOVA p < 0.05 so I reject H0. Now I will do Tukey HSD to see which years differ.\n")
print(TukeyHSD(anova_model))
} else {
cat("ANOVA p >= 0.05 so I do not reject H0. I will not do post-hoc tests.\n")
}
# Step 1: Combine Date and Time columns into a DateTime column
PortTalbotAQ$DateTime <- as.POSIXct(paste(PortTalbotAQ$Date, PortTalbotAQ$Time), format="%Y-%m-%d %H:%M:%S")
# Step 1: Combine Date and Time columns into a DateTime column
PortTalbotAQ$DateTime <- as.POSIXct(paste(PortTalbotAQ$Date, PortTalbotAQ$Time), format="%Y-%m-%d %H:%M:%S")
